﻿using System;
using System.Collections.Generic;
using Gimela.Net.Http.Headers;

namespace Gimela.Net.Http.Authentication
{
  /// <summary>
  /// Provides authentication in the web server.
  /// </summary>
  /// <remarks>
  /// To initiate authentication you just need to throw a Una
  /// </remarks>
  public class AuthenticationProvider
  {
    private readonly Dictionary<string, IAuthenticator> _authenticators =
        new Dictionary<string, IAuthenticator>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Add a authenticator.
    /// </summary>
    /// <param name="authenticator"></param>
    public void Add(IAuthenticator authenticator)
    {
      _authenticators.Add(authenticator.Scheme, authenticator);
    }

    /// <summary>
    /// Authenticate request.
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    /// <remarks>
    /// Requires that a <c>AuthorizationHeader</c> have been sent by the client. If not,
    /// request one by sending a WWW-Authentication header (can be generated by the Challenge method).
    /// </remarks>
    /// <exception cref="InvalidOperationException">Authorization header was not found in the request.</exception>
    /// <exception cref="NotSupportedException">Requested authentication scheme is not supported.</exception>
    public IAuthenticationUser Authenticate(IRequest request)
    {
      var authHeader = request.Headers[AuthorizationHeader.AuthorizationName] as AuthorizationHeader;
      if (authHeader == null)
        throw new InvalidOperationException(AuthorizationHeader.AuthorizationName + " header was not found in the request.");

      IAuthenticator authenticator;
      if (!_authenticators.TryGetValue(authHeader.Scheme, out authenticator))
        throw new NotSupportedException(authHeader.Scheme + " is not supported (no authenticator was found).");

      return authenticator.Authenticate(authHeader, request.Uri.Host, request.Method.ToString().ToLower());
    }

    /// <summary>
    /// Create a challenge header (WWW-authenticate)
    /// </summary>
    /// <param name="response">Response that the authentication header should be added to</param>
    /// <param name="realm">Realm that the user should authenticate in</param>
    /// <returns>WWW-Authenticate header.</returns>
    /// <remarks>
    /// <para>
    /// Scheme can currently be <c>basic</c> or <c>digest</c>. Basic is not very safe, but easier to use.
    /// Digest is quite safe.
    /// </para><para>
    /// </para>
    /// </remarks>
    /// <exception cref="NotSupportedException">Requested scheme is not supported.</exception>
    public void CreateChallenge(IResponse response, string realm)
    {
      foreach (var authenticator in _authenticators.Values)
      {
        var header = authenticator.CreateChallenge(realm);
        response.Add(header.Name, header);
      }
    }
  }
}